2024.6.16 ユニバーサル関数の活用による高速化【numpy】
NumPyやPyTorchが提供する数学関数はユニバーサル関数であるため、うまく利用するとループ処理を排することにより処理を高速化することができる。
ユニバーサル関数でない例
code:univ01.py
import math
x = 1, 2, 3, 4, 5
y = []
for i in x:
y.append(math.sqrt(i)) # (A)
print(x)
print(y)
Mathが提供する数学関数はユニバーサル関数ではないので、(A)のように値をループ処理によって値を1個ずつ求める必要がある。
ユニバーサル関数の例
code:univ02.py
import numpy as np
x = 1, 2, 3, 4, 5
y = np.sqrt(x) # (B)
print(x)
print(y)
NumPyが提供する数学関数はユニバーサル関数なので、(B)のように渡した複数の値について、値ごとに関数処理を行った結果を格納した同じ形状のndarrayを返す。
ユニバーサルなユーザ関数を作る例を示す。負の値に対しては指数関数、生の値に対しては余弦関数となる関数を作ることを考える。
$ y(x) = \left\{ \begin{array}{l} \exp(x), x \leq 0 \\ \cos(x) , x > 0 \end{array}\right.
まずはユニバーサル関数ではない例を示す。
code:univ03.py
import math as np
import matplotlib.pyplot as plt
def no_univ(x):
if x <= 0:
return np.exp(x)
elif x > 0:
return np.cos(x)
x = np.linspace(-2*np.pi, 2*np.pi)
y = []
for i in x:
y.append(no_univ(i)) # スカラ型の値が返されるので、リストで保持する
plt.plot(x, y)
plt.show()
各関数値を求めるためにループ処理を行う必要があるため、低速な処理が行われることが予想される。
ブールインデックス参照を用いると、次のようにループ構造を排することができる。
code:univ04.py
import numpy as np
import matplotlib.pyplot as plt
def univ(x):
y = np.zeros_like(x)
yx <= 0 = np.exp(xx <= 0)
yx > 0 = np.cos(xx > 0)
return y
x = np.linspace(-2*np.pi, 2*np.pi)
y = univ(x) # 戻り値yはxと同じ形状のndarray
plt.plot(x, y)
plt.show()
計算時間を比較しよう。
code:univ05.py
import math, time
import numpy as np
def no_univ(x):
if x <= 0:
return math.exp(x)
elif x > 0:
return math.cos(x)
def univ(x):
y = np.zeros_like(x)
yx <= 0 = np.exp(xx <= 0)
yx > 0 = np.cos(xx > 0)
return y
loopmax = 10000
datanum = 10
x = np.linspace(-2*np.pi, 2*np.pi, datanum)
y = []
time_s1 = time.process_time()
# 非ユニバーサル関数
for loop in range(loopmax):
for i in x:
# y.append(no_univ(i))
no_univ(i)
time_duration1 = time.process_time() - time_s1
# ユニバーサル関数
time_s2 = time.process_time()
for i in range(loopmax):
# y = univ(x)
univ(x)
time_duration2 = time.process_time() - time_s2
print('loopmax, datanum:', loopmax, datanum)
print('no-univ: {:.6}'.format(time_duration1))
print('univ : {:.6}'.format(time_duration2))
print('ratio : {:.4}'.format(time_duration2 / time_duration1))
1回ループあたりの計算回数が多いほど(datanum)、ユニバーサル関数の方が高速動作する傾向にあることを確認できる。
loopmax = 10000としたとき、
datanum: 10
univ : 0.0513561 / no-univ: 0.607378
ratio : 0.08455
datanum: 1000
univ : 0.101154 / no-univ: 1.45954
ratio : 0.06931
datanum: 100000
univ : 7.63716 / no-univ: 106.554
ratio : 0.07167
実行する度に変動はあるが、総じて1/10以下の計算時間を達成している。